// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Globalization;

namespace SnapObjects.Commons
{
    public static class Utils
    {
        public static bool IgnoreStartPunctuationEquals(string value, string target)
        {
            value = value.Trim();

            if (Char.IsPunctuation(value[0]))
            {
                value = value.Substring(1);
            }

            if (Char.IsPunctuation(target[0]))
            {
                target = target.Substring(1);
            }

            return value.Equals(target, StringComparison.OrdinalIgnoreCase);
        }

        public static string TrimStartPunctuation(string value)
        {
            if (Char.IsPunctuation(value[0]))
            {
                value = value.Substring(1);
            }

            return value;
        }

        public static object ChangeType(object value, Type type)
        {
            if (value == null)
            {
                return value;
            }

            var vtype = value.GetType();

            if (vtype == type)
            {
                return value;
            }
            else
            {
                type = Nullable.GetUnderlyingType(type) ?? type;

                if (type == typeof(TimeSpan))
                {
                    return ToTimeSpan(value);
                }
                else if (type == typeof(Guid))
                {
                    return Guid.Parse(value.ToString());
                }
                else if (type.IsEnum)
                {
                    return Enum.Parse(type, value.ToString());
                }
                else if (type == typeof(DateTime))
                {
                    return ToDateTime(value);
                }
                else if (type == typeof(DateTimeOffset))
                {
                    return ToDateTimeOffset(value);
                }
                else if (type == typeof(bool))
                {
                    return ToBoolean(value);
                }
                else
                {
                    return Convert.ChangeType(value, type);
                }
            }
        }

        public static DateTime ToDateTime(object value)
        {
            if (value is DateTime dt)
            {
                return dt;
            }

            if (DateTime.TryParse(value.ToString(), out var dateTime))
            {
                return dateTime;
            }
            else
            {
                return DateTime.ParseExact(value.ToString(),
                    new string[] { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss.ffffff", "yyyy-MM-dd HH:mm:ss:ffffff" },
                    null, DateTimeStyles.None);
            }
        }

        public static TimeSpan ToTimeSpan(object value)
        {
            if (value is TimeSpan time)
            {
                return time;
            }

            if (DateTime.TryParse(value.ToString(), out var dt))
            {
            }
            else
            {
                dt = DateTime.ParseExact(value.ToString(),
                new string[] { "HH:mm:ss.ffffff", "HH:mm:ss:ffffff" }, null, DateTimeStyles.None);
            }

            return new TimeSpan(0, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
        }

        public static string ToUpperTitle(string value)
        {
            if (value.Length > 0)
            {
                var arr = value.ToCharArray();

                var prevChar = Char.MinValue;

                for (var i = 0; i < arr.Length; i++)
                {
                    if (i == 0)
                    {
                        arr[i] = Char.ToUpper(arr[i]);
                    }
                    else if (!Char.IsLetter(prevChar))
                    {
                        arr[i] = Char.ToUpper(arr[i]);
                    }

                    prevChar = arr[i];
                }

                value = String.Join("", arr);
            }
            return value;
        }

        public static double ToDouble(object obj)
        {
            var type = obj.GetType();

            type = type.ContainsGenericParameters ? type.GetGenericArguments()[0] : type;

            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Byte: return (int)obj;
                case TypeCode.SByte: return (int)obj;
                case TypeCode.Int16: return (int)obj;
                case TypeCode.Int32: return (int)obj;
                case TypeCode.Int64: return (long)obj;
                case TypeCode.Double: return (double)obj;
                case TypeCode.Decimal: return (double)(decimal)obj;
                case TypeCode.Single: return (float)obj;
                default: return Double.NaN;
            }
        }

        public static bool IsDecimal(object obj, out decimal value)
        {
            var type = obj.GetType();

            type = type.ContainsGenericParameters ? type.GetGenericArguments()[0] : type;

            var isDecimal = Type.GetTypeCode(type) == TypeCode.Decimal;

            value = isDecimal ? (decimal)obj : Decimal.MinusOne;

            return isDecimal;
        }

        public static bool IsAssignableFromGenericInterface(Type genericType, Type entryType)
        {
            if (entryType.IsInterface)
            {
                if (genericType.Name == entryType.Name)
                {
                    return true;
                }
            }
            else
            {
                var inerfaces = entryType.GetInterfaces();

                foreach (var inerface in inerfaces)
                {
                    if (inerface.IsGenericType)
                    {
                        var inerfaceType = inerface.GetGenericTypeDefinition();
                        if (genericType == inerfaceType)
                        {
                            return true;
                        }
                    }
                    else
                    {
                        if (genericType == inerface)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private static DateTimeOffset? ToDateTimeOffset(object value)
        {
            if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
            {
                return null;
            }
            else if (value is DateTimeOffset dtoffset)
            {
                return dtoffset;
            }
            else if (value is DateTime dt)
            {
                try
                {
                    return new DateTimeOffset(dt, dt.TimeOfDay);
                }
                catch
                {
                    return new DateTimeOffset(dt);
                }
            }
            else if (DateTimeOffset.TryParse(value.ToString(), out var dateTime))
            {
                return dateTime;
            }
            else
            {
                string[] formats = new string[] {
                    @"@M/dd/yyyy HH:m zzz",
                    @"MM/dd/yyyy HH:m zzz",
                    @"M/d/yyyy HH:m zzz",
                    @"MM/d/yyyy HH:m zzz",
                    @"M/dd/yy HH:m zzz",
                    @"MM/dd/yy HH:m zzz",
                    @"M/d/yy HH:m zzz",
                    @"MM/d/yy HH:m zzz",
                    @"M/dd/yyyy H:m zzz",
                    @"MM/dd/yyyy H:m zzz",
                    @"M/d/yyyy H:m zzz",
                    @"MM/d/yyyy H:m zzz",
                    @"M/dd/yy H:m zzz",
                    @"MM/dd/yy H:m zzz",
                    @"M/d/yy H:m zzz",
                    @"MM/d/yy H:m zzz",
                    @"M/dd/yyyy HH:mm zzz",
                    @"MM/dd/yyyy HH:mm zzz",
                    @"M/d/yyyy HH:mm zzz",
                    @"MM/d/yyyy HH:mm zzz",
                    @"M/dd/yy HH:mm zzz",
                    @"MM/dd/yy HH:mm zzz",
                    @"M/d/yy HH:mm zzz",
                    @"MM/d/yy HH:mm zzz",
                    @"M/dd/yyyy H:mm zzz",
                    @"MM/dd/yyyy H:mm zzz",
                    @"M/d/yyyy H:mm zzz",
                    @"MM/d/yyyy H:mm zzz",
                    @"M/dd/yy H:mm zzz",
                    @"MM/dd/yy H:mm zzz",
                    @"M/d/yy H:mm zzz",
                    @"MM/d/yy H:mm zzz"};

                return DateTimeOffset.ParseExact(value.ToString(), formats, null, DateTimeStyles.None);
            }
        }

        private static bool ToBoolean(object value)
        {
            if (value is bool b)
            {
                return b;
            }
            else if (Boolean.TryParse(value.ToString(), out b))
            {
                return b;
            }
            else if (Int32.TryParse(value.ToString(), out var n))
            {
                return Convert.ToBoolean(n);
            }
            else
            {
                return Convert.ToBoolean(value);
            }
        }
    }
}
